<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage grove
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/**
 *
 */

namespace mg;

interface Filter {

}

interface FilterProvider {

    public function getFilter($name);
}

class DefaultFilterProvider implements FilterProvider {

    private $filters = null;
    private $properties = null;

    private $filterCache = null;

    private $nullFilter = null;

    public function __construct(Runtime $runtime, StorageManager $storageManager) {
        $this->filters 		= $storageManager->cache($storageManager->getStorage('defaultfilterprovider.filters', true));
        $this->properties   = $storageManager->cache($storageManager->getStorage('defaultfilterprovider.properties', true));

        $this->filterCache = array();

        if($runtime->isTransactional()) {
            $this->rebuild();
        }

        $this->nullFilter = new NullFilter();
    }

    public function rebuild() {

        $architecture = in(function(Architecture $architecture) { return $architecture; });

        $updates = $architecture->getUpdates();

        $filterClasses = isset($this->properties['filterClasses']) ? unserialize($this->properties['filterClasses']) : array();

        // first remove all knowledge about filters which are modified

        /* @var $update ClassUpdate */
        foreach($updates as $update) {
            $className = mb_strtolower($update->getQualifiedName());

            // if filterclass is removed, removal from the filterset is safe
            if(isset($filterClasses[$className])) {
                $filterName = $filterClasses[$className];
                unset($this->filters[$filterName]);
                unset($filterClasses[$className]);
            }
        }

        // next, rebuild this knowledge

        foreach($updates as $update) {
            if($update->isRemoved()) {
                continue;
            }

            $className = mb_strtolower($update->getQualifiedName());

            $distance = $architecture->getClassDistance($className, 'mg\Filter');

            if($distance === false || $distance === 0) {
                continue;
            }

            /* @var $class ClassHeader */
            $class = $architecture->getClass($className);

            $constants = $class->getConstants();

            $name = isset($constants['name']) ? mb_strtolower($constants['name']) : null;

            $name = trim($name, '"\'`');

            // nothing to be done
            if($name === null) {
                continue;
            }

            if(isset($this->filters[$name])) {
                throw new Exception("Duplicate mg\Filter name for '{$name}'");
            }


            $this->filters[$name] = $className;
            $filterClasses[$className] = $name;
        }

        $this->properties['filterClasses'] = serialize($filterClasses);

    }

    public function getFilter($name) {
        $name = mb_strtolower($name);

        if(isset($this->filtersCache[$name])) {
            return $this->filtersCache[$name];
        }

        if(isset($this->filters[$name])) {
            $filter = get($this->filters[$name]);
            $this->filtersCache[$name] = $filter;
        } else {
            $filter = $this->nullFilter;
        }

        return $filter;
    }

}

class NullFilter implements Filter {
    public function filter() {
        return null;
    }
}

class LowerFilter implements Filter {

    const name = "lower";

    public function filter($object) {
        return mb_strtolower($object);
    }

}

class UpperCaseFirstFilter implements Filter {

    const name = "ucfirst";

    public function filter($object) {
        return ucfirst($object);
    }

}

class UppercaseFilter implements Filter {

    const name = "upper";

    public function filter($object) {
        return mb_strtoupper($object);
    }
}

class HTMLEntitiesFilter implements Filter, I18N {

    const name = "escape";

    private $charset;

    public function __construct(G11N $g11n) {
        $g11n->addI18NListener($this);
        $this->charset = $g11n->getCharset();
    }

    public function charsetChanged($charset) {
        $this->charset = $charset;
    }

    public function filter($object, $type = 'html') {
        $object = (string) $object;

        if($type === 'html') {
            return htmlentities($object, ENT_QUOTES, $this->charset);
        } elseif($type === 'xml') {
            return htmlspecialchars($object, ENT_QUOTES, $this->charset);
        }

        return null;
    }

}

class TruncateFilter implements Filter {

    const name = "truncate";

    public function filter($object, $length = 20, $postfix = '..') {
        $object = (string) $object;
        if(mb_strlen($object) > $length) {
            $object = mb_substr($object, 0, $length - mb_strlen($postfix)) . $postfix;
        }

        return $object;
    }

}

class IfEmptyFilter implements Filter {

    const name = "ifempty";

    public function filter($object, $else) {
        return empty($object) ? $else : $object;
    }
}

class CustomFilter implements Filter {

    const name = "custom";

    public function filter($object, \Closure $closure) {
        return $closure($object);
    }
}

class FeedbackFilter implements Filter {

    const name = "feedback";

    /**
     * @var \mg\Feedback
     */
    private $feedback;

    public function __construct(Feedback $feedback) {
        $this->feedback = $feedback;
    }

    public function filter($object, $type = null) {
        switch($type) {
            case Feedback :: FEEDBACK_DEBUG :
            case Feedback :: FEEDBACK_INFO :
            case Feedback :: FEEDBACK_ERROR :
            case Feedback :: FEEDBACK_WARNING :
                return $this->feedback->getMessages($object, $type);
            default :
                return $this->feedback->getMessages($object);
        }
    }
}

class ReverseFilter implements Filter {

    const name = "reverse";

    public function filter($object) {
        $object = strval($object);
        $result = '';

        for($i =  mb_strlen($object) - 1; $i >= 0; $i--) {
            $result .= mb_substr($object, $i, 1);
        }

        return $result;
    }
}

// Convert to INTL
class DateFilter implements Filter, L10N {

    const name = "date";

    private $locale = null;

    public function __construct(G11N $g11n) {
        $g11n->addL10NListener($this);
        $this->locale = $g11n->getLocale();
    }

    public function localeChanged(Locale $locale) {
        $this->locale = $locale;
    }

    public function filter($object, $format = "F j, Y, g:i a") {
        return date($format, strtotime($object));
    }
}
